package spring.security.core.jackson;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

public class JsonReturnValueHandler implements HandlerMethodReturnValueHandler, BeanPostProcessor {

	private static final Logger logger = LoggerFactory.getLogger(JsonReturnValueHandler.class);
	private List<ResponseBodyAdvice<Object>> advices = new ArrayList<>();
	private ObjectMapper objectMapper;
	
	public JsonReturnValueHandler(ObjectMapper objectMapper) {
		this.objectMapper = objectMapper;
	}

	public JsonReturnValueHandler(MappingJackson2HttpMessageConverter messageConverter) {
		this.objectMapper = messageConverter.getObjectMapper();
	}
	
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean instanceof ResponseBodyAdvice) {
			this.advices.add((ResponseBodyAdvice<Object>) bean);
		} else if (bean instanceof RequestMappingHandlerAdapter) {
			List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(((RequestMappingHandlerAdapter) bean).getReturnValueHandlers());
			JsonReturnValueHandler jsonHandler = null;

			for (int adapter = 0; adapter < handlers.size(); ++adapter) {
				HandlerMethodReturnValueHandler handler = (HandlerMethodReturnValueHandler) handlers.get(adapter);
				if (handler instanceof JsonReturnValueHandler) {
					jsonHandler = (JsonReturnValueHandler) handler;
					break;
				}
			}

			if (jsonHandler != null) {
				handlers.remove(jsonHandler);
				handlers.add(0, jsonHandler);
				RequestMappingHandlerAdapter arg6 = (RequestMappingHandlerAdapter) bean;
				arg6.setReturnValueHandlers(handlers);
			}
		}

		return bean;
	}

	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return returnType.getMethodAnnotation(JSON.class) != null;
	}

	@Override
	public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest) throws Exception {
		HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse(HttpServletResponse.class);
		HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(HttpServletRequest.class);

		ResponseBodyAdvice<Object> jsonStr;
		for (int jsonSerializer = 0; jsonSerializer < this.advices.size(); ++jsonSerializer) {
			jsonStr = (ResponseBodyAdvice<Object>) this.advices.get(jsonSerializer);
			if (jsonStr.supports(returnType, null)) {
				returnValue = jsonStr.beforeBodyWrite(returnValue, returnType, MediaType.APPLICATION_JSON_UTF8,
						null, new ServletServerHttpRequest(request), new ServletServerHttpResponse(response));
			}
		}

		JsonDyncSerializer arg9 = new JsonDyncSerializer(this.objectMapper);
		jsonStr = null;
		JSON json = (JSON) returnType.getMethodAnnotation(JSON.class);
		if (json != null) {
			arg9.filter(json.type(), json.includes(), json.excludes());
		}

		String arg10 = arg9.toJson(returnValue);
		if (arg10 != null) {
			mavContainer.setRequestHandled(true);
			if (logger.isDebugEnabled()) {
				logger.debug("Written [" + arg10 + "] as \"" + MediaType.APPLICATION_JSON_UTF8
						+ "\" using [ JsonReturnHandler ]");
			}

			response.setCharacterEncoding("UTF-8");
			response.setContentType("application/json;charset=UTF-8");
			response.getWriter().write(arg10);
		}
	}

}
